﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;

namespace Milan.Net
{
	/// <summary>
	/// A generic RPC server as an InternalService
	/// </summary>
	public abstract class RpcServer : Milan.Deploy.ThreadScanner
	{
		public RpcServer(Milan.Deploy.Application application, string pipeName, int maximumThreads) : base(application)
		{
			m_pipeName = pipeName;
			m_maximumThreads = maximumThreads;
		}
		private string m_pipeName;
		private int m_maximumThreads;
		private NamedPipeServerStream m_pipeServer;
		private void NewPipeConnection(IAsyncResult iAsyncResult)
		{
			//Console.WriteLine("Client connected on thread[{0}].", threadId);
			try
			{
				if (m_pipeServer.IsConnected)
				{
					m_pipeServer.EndWaitForConnection(iAsyncResult);
					// Read the request from the client. Once the client has
					// written to the pipe its security token will be available.
					StreamString ss = new StreamString(m_pipeServer);

					// Verify our identity to the connected client using a
					// string that the client anticipates.
					ss.WriteString("{OK}");
					string command = ss.ReadString();
					string response = ProcessString(command);
					ss.WriteString(response);
				}

				// Display the name of the user we are impersonating.
				//				Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.",	filename, threadId, pipeServer.GetImpersonationUserName());
			}
			// Catch the IOException that is raised if the pipe is broken
			// or disconnected.
			catch (Exception)
			{
				//Console.WriteLine("ERROR: {0}", e.Message);
			}
		}
		protected override void Run()
		{
			using (m_pipeServer = new NamedPipeServerStream(m_pipeName, PipeDirection.InOut, m_maximumThreads, PipeTransmissionMode.Message, PipeOptions.Asynchronous))
			{
				// Wait for a client to connect
				IAsyncResult iAsyncResult = m_pipeServer.BeginWaitForConnection(NewPipeConnection, this);
				this.QuitEvent.WaitOne();
			}
		}
		/// <summary>
		/// Override this incoming string processing
		/// </summary>
		/// <param name="command"></param>
		public abstract string ProcessString(string command);
		// Defines the data protocol for reading and writing strings on our stream
		public class StreamString
		{
			private Stream ioStream;
			private UnicodeEncoding streamEncoding;

			public StreamString(Stream ioStream)
			{
				this.ioStream = ioStream;
				streamEncoding = new UnicodeEncoding();
			}

			/// <summary>
			/// Reads a string which has a length specifier and data, which follows
			/// </summary>
			/// <returns>Returns a string</returns>
			public string ReadString()
			{
				// Make sure that our leader byte is a 2
				Byte[] leaderByte = new Byte[1];
				do
				{
					ioStream.Read(leaderByte, 0, 1);
				}while (leaderByte[0] != 2);
				// After the leader byte is the length of this segment
				Byte[] lengthBuffer = new Byte[8];
				ioStream.Read(lengthBuffer, 0, 8);
				int len = Convert.ToInt32(System.Text.Encoding.UTF8.GetString(lengthBuffer),16);
				// Now read the actual data and convert it into an internal string
				byte[] inBuffer = new byte[len];
				ioStream.Read(inBuffer, 0, len);
				return System.Text.Encoding.UTF8.GetString(inBuffer);
			}

			/// <summary>
			/// Writes a length
			/// </summary>
			/// <param name="outString">internal format string to send out on the wire</param>
			/// <returns></returns>
			public int WriteString(string outString)
			{
				byte[] outBuffer = System.Text.Encoding.UTF8.GetBytes(outString);
				int len = outBuffer.Length;
				byte[] header = System.Text.Encoding.UTF8.GetBytes(len.ToString("X8"));
				ioStream.Write(new Byte[]{2}, 0, 1);	// Send an ASCII START TEXT (02)
				ioStream.Write(header, 0, 8);			// Write the length of the data
				ioStream.Write(outBuffer, 0, len);		// Here is the data
				ioStream.Flush();

				return outBuffer.Length + 2;
			}
		}

		// Contains the method executed in the context of the impersonated user
		public class ReadFileToStream
		{
			private string fn;
			private StreamString ss;

			public ReadFileToStream(StreamString str, string filename)
			{
				fn = filename;
				ss = str;
			}

			public void Start()
			{
				string contents = File.ReadAllText(fn);
				ss.WriteString(contents);
			}
		}
	}
}
